using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Objects;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace ELinqParser
{
	internal class ELinqParser
	{
		ELinqParser()
		{
		}

		public static SelectModel ParseModel(Expression e)
		{
			return ParseRoot(e);
		}

		static SelectModel ParseRoot(Expression e)
		{
			MethodCallExpression me;

			if (IsExtensionCall(e, LinqKnownMembers.QueryableSelect1, out me))
				// from db... select ...
				return ParseSelect(me);

			if (IsMergeAs(e, out me))
				return ParseDynamic(me);
			throw new Exception();
		}

		static SelectModel ParseDynamic(MethodCallExpression e)
		{
			object value;
			if (IsConstant(e.Object, out value))
			{
				var setType = value.GetType();
				if (setType.IsGenericType)
				{
					Type entityType = setType.GetGenericArguments()[0];
					return new SelectModel { What = new[] { new SelectableEntity { EntityType = entityType } } };
				}
			}
			throw new Exception();
		}

		static SelectModel ParseSelect(MethodCallExpression e)
		{
			var select = new SelectModel();
			select.What = ParseWhats(e.Arguments);
			return select;
		}

		static Selectable[] ParseWhats(ReadOnlyCollection<Expression> e)
		{
			Debug.Assert(e.Count > 1);
			var result = new List<Selectable>();
			var from = ParseFrom(e[0]);
			for (int i = 1; i < e.Count; i++)
				result.Add(ParseLambdaType(e[i], null));	
			return result.ToArray();
		}

		static Selectable ParseLambdaType(Expression e, SelectModel source)
		{
			LambdaExpression le;
			if (IsLambdaQuote(e, out le))
			{
				switch(le.Body.NodeType)
				{

					case ExpressionType.New:
						// select new {...}
						return ParseNew(le.Body, le.Parameters, source);

					case ExpressionType.Parameter:
						// from c select c
						return ParseEntityAsParam(le);
				}
			}
			throw new Exception();
		}

		static Selectable ParseEntityAsParam(LambdaExpression le)
		{
			var pe = (ParameterExpression) le.Body;
			ParameterExpression inputParam = le.Parameters.First(p => p.Name == pe.Name);
			return new SelectableEntity {EntityType = inputParam.Type};
		}

		static Selectable ParseNew(Expression e, ReadOnlyCollection<ParameterExpression> parameters, SelectModel source)
		{
			NewExpression ne;
			if (IsNewAnonymousType(e, out ne))
			{
				var columns = new List<Selectable>();
				for (int i = 0; i < ne.Arguments.Count; i++)
				{
					var arg = ne.Arguments[i];
					columns.Add(ArgToSelectable(arg, ne.Type.GetProperties()[i], parameters, source));
				}
				return new SelectableTuple { Columns = columns.ToArray() };
			}
			throw new Exception();
		}

		static Selectable ArgToSelectable(Expression e, PropertyInfo prop, ReadOnlyCollection<ParameterExpression> parameters, SelectModel source)
		{
			switch (e.NodeType)
			{
				case ExpressionType.MemberAccess:
					// new { F = Company.Name }
					return MemberToSelectable((MemberExpression)e, prop, parameters, source);

				case ExpressionType.Parameter:
					// new { F = Company }
					return EntityAsMember(e, parameters);

				case ExpressionType.Call:
					// new { F = (from ...).Count() }
					return Aggregate(e, prop);
			}
			throw new Exception();
		}

		static Selectable Aggregate(Expression e, PropertyInfo prop)
		{
			MethodCallExpression me;
			if (IsAggregateCall(e, out me))
				return new SelectableAggregate {Name = prop.Name, Function = me.Method};
			throw new Exception();
		}

		static Selectable EntityAsMember(Expression e, ReadOnlyCollection<ParameterExpression> parameters)
		{
			var pe = (ParameterExpression) e;
			ParameterExpression inputParam = parameters.First(p => p.Name == pe.Name);
			return new SelectableEntity {EntityType = inputParam.Type};
		}

		static Selectable MemberToSelectable(MemberExpression e, PropertyInfo anonProp, ReadOnlyCollection<ParameterExpression> parameters, SelectModel source)
		{
			var prop = e.Member as PropertyInfo;
			if (prop != null)
			{
				if (!IsAnonymousType(prop.ReflectedType))
					// { UserName = company.UserName }
					return new SelectableProperty {Name = anonProp.Name, Prop = prop};
				
				if (e.Expression.NodeType == ExpressionType.Parameter)
				{
					// groupby(a => a.X)
					var param = (ParameterExpression) e.Expression;
					var paramIndex = parameters.IndexOf(parameters.First(p => p.Name == param.Name));
					var tuple = (SelectableTuple)source.What[paramIndex];
					var col = tuple.Columns.First(c => c.Name == prop.Name);
					return col;
				}
				
				if (e.Expression.NodeType == ExpressionType.MemberAccess)
				{
					var me = (MemberExpression) e.Expression;
					if (me.Member.Same(LinqKnownMembers.GroupingKey))
					{
						var e2 = me.Expression;
						// g.Key.F
					}
					return MemberToSelectable(me, anonProp, parameters, source);
				}
			}
			throw new Exception();
		}

		private static bool IsAnonymousType(Type type)
		{
			return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute)) && type.Name.StartsWith("<>f__AnonymousType");
		}

		static bool IsNewAnonymousType(Expression e, out NewExpression ne)
		{
			ne = e as NewExpression;
			if(ne != null && IsAnonymousType(ne.Type))
			{
				return true;
			}
			ne = null;
			return false;
		}

		static bool IsLambdaQuote(Expression e, out LambdaExpression le)
		{
			var ue = e as UnaryExpression;
			if (ue != null && ue.NodeType == ExpressionType.Quote)
			{
				le = (LambdaExpression) ue.Operand;
				return true;
			}
			le = null;
			return false;
		}

		static From ParseFrom(Expression e)
		{
			MethodCallExpression me;
			object obj;
			if (IsMergeAs(e, out me) && (IsConstant(me.Object, out obj)))
				return new FromObjectsQuery {Query = (ObjectQuery) obj};

			if (IsExtensionCall(e, LinqKnownMembers.QueryableGroupBy, out me))
				return ParseGroupBy(me);

			throw new Exception();
		}

		static FromGroupBy ParseGroupBy(MethodCallExpression e)
		{ 
			// IQueryable<IGrouping<TKey,TSource>> GroupBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
			var source = ParseRoot(e.Arguments[0]);
			var key = ParseLambdaType(e.Arguments[1], source);
			return new FromGroupBy { Source = source, Key = key};
		}

		static bool IsConstant(Expression e, out object value)
		{
			var ue = e as UnaryExpression;
			if (ue != null && ue.NodeType == ExpressionType.Convert)
			{
				var ce = ue.Operand as ConstantExpression;
				if (ce != null)
				{
					value = ce.Value;
					return true;
				}
			}
			throw new Exception();
		}

		static bool IsExtensionCall(Expression e, MethodInfo methodInfo, out MethodCallExpression me)
		{
			me = e as MethodCallExpression;
			return
				me != null &&
					(me.Method == methodInfo || (me.Method.IsGenericMethod && methodInfo == me.Method.GetGenericMethodDefinition()));
		}

		static bool IsMergeAs(Expression e, out MethodCallExpression me)
		{
			me = e as MethodCallExpression;
			return
				me != null && (me.Method.Name == "MergeAs");
		}

		static bool IsAggregateCall(Expression e, out MethodCallExpression me)
		{
			me = e as MethodCallExpression;
			return
				me != null &&
					me.Method.ReflectedType == typeof (Queryable);
		}
		
	}
}